/*
 * acooly.cn Inc.
 * Copyright (c) 2016 All Rights Reserved.
 * create by zhike
 * date:2016年4月4日
 *
 */
package com.acooly.module.openapi.client.provider.baofu.marshall;

import com.acooly.core.common.exception.BusinessException;
import com.acooly.core.utils.Reflections;
import com.acooly.core.utils.Strings;
import com.acooly.core.utils.security.RSA;
import com.acooly.core.utils.validate.Validators;
import com.acooly.module.openapi.client.api.exception.ApiClientException;
import com.acooly.module.openapi.client.provider.baofu.BaoFuConstants;
import com.acooly.module.openapi.client.provider.baofu.OpenAPIClientBaoFuProperties;
import com.acooly.module.openapi.client.provider.baofu.domain.BaoFuApiMessage;
import com.acooly.module.openapi.client.provider.baofu.domain.BaoFuRequest;
import com.acooly.module.openapi.client.provider.baofu.enums.BaoFuServiceIsZIPEnum;
import com.acooly.module.openapi.client.provider.baofu.support.BaoFuAlias;
import com.acooly.module.openapi.client.provider.baofu.utils.*;
import com.acooly.module.safety.Safes;
import com.acooly.module.safety.key.KeyLoadManager;
import com.acooly.module.safety.signature.SignTypeEnum;
import com.acooly.module.safety.signature.SignerFactory;
import com.acooly.module.safety.support.CodecEnum;
import com.acooly.module.safety.support.KeyStoreInfo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.fastjson.parser.Feature;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Autowired;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.*;

/**
 * @author zhike
 */
@Slf4j
public class BaoFuMarshallSupport {

    @Autowired
    protected OpenAPIClientBaoFuProperties openAPIClientBaoFuProperties;

    @Autowired
    protected SignerFactory signerFactory;

    @Autowired
    private KeyLoadManager keyStoreLoadManager;

    private volatile KeyStoreInfo keyStoreInfo;

    private static JsonMarshallor jsonMarshallor = JsonMarshallor.INSTANCE;

    protected OpenAPIClientBaoFuProperties getProperties() {
        return openAPIClientBaoFuProperties;
    }

    public SignerFactory getSignerFactory() {
        return signerFactory;
    }

    protected SortedMap<String, String> doMarshall(BaoFuRequest source) {
        doVerifyParam(source);
        SortedMap<String, String> signDataMap = getSignDataMap(source);
        Map<Object, Object> signParams = Maps.newHashMap();
        signParams.putAll(signDataMap);
        String signStr = MapToXMLString.converter(signParams,BaoFuConstants.BAOFU_DATA_CONTENT);
        log.info("待签字符串:{}", signStr);
        boolean isZip = false;
        try {
            BaoFuServiceIsZIPEnum baoFuServiceIsZIP = BaoFuServiceIsZIPEnum.find(source.getService());
            if(baoFuServiceIsZIP == null) {
                signStr = BaoFuSecurityUtil.Base64Encode(signStr);
            } else {
                isZip = true;
            }
        }catch (Exception e) {
            log.info("Base64代签字符串失败：{}",e.getMessage());
            throw new ApiClientException(e.getMessage());
        }
        source.setDataContent(doSign(signStr, source,isZip));
        SortedMap<String, String> requestDataMap = getRequestDataMap(source);
        return requestDataMap;
    }

    /**
     * 获取代签map
     *
     * @param source
     * @return
     */
    protected SortedMap<String, String> getSignDataMap(BaoFuRequest source) {
        SortedMap<String, String> signData = Maps.newTreeMap();
        Set<Field> fields = Reflections.getFields(source.getClass());
        String key = null;
        Object value = null;
        for (Field field : fields) {
            value = Reflections.getFieldValue(source, field.getName());
            if (value == null) {
                continue;
            }
            BaoFuAlias baofuAlias = field.getAnnotation(BaoFuAlias.class);
            if (baofuAlias == null) {
                continue;
            } else {
                if (!baofuAlias.sign()) {
                    continue;
                }
                //对list类型做特殊处理
                if(field.getType().isAssignableFrom(List.class)) {
                    String subObjectJsonStr = jsonMarshallor.marshall(value);
                    StringBuilder stringBuilder = new StringBuilder();
                    List<LinkedHashMap<String, Object>> listMap = JSON.parseObject(subObjectJsonStr,new TypeReference<List<LinkedHashMap<String, Object>>>(){} , Feature.OrderedField);
                    for(LinkedHashMap<String, Object> subMap:listMap) {
                        Iterator iterator = subMap.entrySet().iterator();
                        String subValue = "<info>";
                        while (iterator.hasNext()) {
                            Map.Entry entry = (Map.Entry) iterator.next();
                            if(entry.getValue() != null) {
                                subValue+=entry.getValue()+"#";
                            }
                        }
                        if(subValue.endsWith("#")) {
                            subValue = subValue.substring(0,subValue.length()-1);
                        }
                        subValue += "</info>";
                        stringBuilder.append(subValue);
                    }
                    value = stringBuilder.toString();
                }
                key = baofuAlias.value();
            }
            if (Strings.isNotBlank((String) value)) {
                signData.put(key, (String) value);
            }
        }
        return signData;
    }

    /**
     * 组装二级子对象报文
     * @param signData
     * @param subFields
     */
    public void getSubSignData(SortedMap<String, String> signData,Set<Field> subFields) {
        String key = null;
        Object value = null;
        for (Field subField:subFields) {
            value = Reflections.getFieldValue(subField, subField.getName());
            if (value == null) {
                continue;
            }
            BaoFuAlias baofuAlias = subField.getAnnotation(BaoFuAlias.class);
            if (baofuAlias == null) {
                continue;
            } else {
                if (!baofuAlias.sign()) {
                    continue;
                }
                key = baofuAlias.value();
            }
            if (Strings.isNotBlank((String) value)) {
                signData.put(key, (String) value);
            }
        }
    }
    /**
     * 获取代签map
     *
     * @param source
     * @return
     */
    protected SortedMap<String, String> getRequestDataMap(Object source) {
        SortedMap<String, String> requestData = Maps.newTreeMap();
        Set<Field> fields = Reflections.getFields(source.getClass());
        String key = null;
        Object value = null;
        for (Field field : fields) {
            value = Reflections.getFieldValue(source, field.getName());
            if (value == null) {
                continue;
            }
            BaoFuAlias baofuAlias = field.getAnnotation(BaoFuAlias.class);
            if (baofuAlias == null) {
                continue;
            } else {
                if (!baofuAlias.request()) {
                    continue;
                }
                key = baofuAlias.value();
            }
            if (Strings.isNotBlank((String) value)) {
                requestData.put(key, (String) value);
            }
        }
        return requestData;
    }
    /**
     * 将对象转化为String
     *
     * @param object
     * @return
     */
    private String convertString(Object object) {
        if (object == null) {
            return null;
        }
        return object.toString();
    }

    /**
     * 校验参数
     *
     * @param source
     */
    protected void doVerifyParam(BaoFuApiMessage source) {
        try {
            source.doCheck();
            Validators.assertJSR303(source);
        } catch (Exception e) {
            throw new ApiClientException(e.getMessage());
        }
    }

    /**
     * 签名 优先获取传进来的，如果传进来没有则用配置文件中的
     *
     * @param waitForSign
     * @param source
     * @return
     */
    protected String doSign(String waitForSign, BaoFuRequest source,boolean isZip) {
        KeyStoreInfo key = getKeyInfo(source);
        PrivateKey privateKey = key.getPrivateKey();
        String signStr = BaoFuSecurityUtil.encryptByPrivateKey(waitForSign,privateKey,isZip);
        return signStr;
    }

    protected String doSign(String waitForSign, String key) {
        String signStr = Safes.getSigner(SignTypeEnum.Cert).sign(waitForSign, key);
        return signStr;
    }

    /**
     * 解密响应报文
     *
     * @param encodeDataContent
     * @return
     */
    protected String doDecodeResponse(String encodeDataContent,String partnerId,boolean isZip) {
        KeyStoreInfo key = getKeyInfo(partnerId);
        PublicKey publicKey = key.getCertificate().getPublicKey();
        return decryptByPublicKey(encodeDataContent,publicKey,isZip);
    }

    /**
     * 根据公钥解密
     *
     * @param src
     * @param publicKey
     * @return
     */
    public String decryptByPublicKey(String src, PublicKey publicKey,boolean isZip) {

        try {
            byte[] destBytes = rsaByPublicKey(StringHelper.hex2Bytes(src), publicKey, Cipher.DECRYPT_MODE);

            if (destBytes == null) {
                throw new BusinessException("公钥解密失败");
            }
            if(isZip) {
                return new String(ZipUtils.unZip(destBytes), BaoFuConstants.ENCODE);
            }else {
                return new String(destBytes, BaoFuConstants.ENCODE);
            }
        } catch (UnsupportedEncodingException e) {
            log.error("解密内容不是正确的UTF8格式:", e);
            throw new BusinessException("公钥解密失败:"+e.getMessage());
        }
    }

    /**
     * 公钥算法
     *
     * @param srcData   源字节
     * @param publicKey 公钥
     * @param mode      加密 OR 解密
     * @return
     */
    public byte[] rsaByPublicKey(byte[] srcData, PublicKey publicKey, int mode) {
        try {
            Cipher cipher = Cipher.getInstance(BaoFuConstants.RSA_CHIPER);
            cipher.init(mode, publicKey);
            // 分段加密
            int blockSize = (mode == Cipher.ENCRYPT_MODE) ? BaoFuConstants.ENCRYPT_KEYSIZE : BaoFuConstants.DECRYPT_KEYSIZE;
            byte[] encryptedData = null;
            for (int i = 0; i < srcData.length; i += blockSize) {
                // 注意要使用2的倍数，否则会出现加密后的内容再解密时为乱码
                byte[] doFinal = cipher.doFinal(ArrayUtils.subarray(srcData, i, i + blockSize));
                encryptedData = ArrayUtils.addAll(encryptedData, doFinal);
            }
            return encryptedData;
        } catch (NoSuchAlgorithmException e) {
            log.error("公钥算法-不存在的解密算法:", e);
        } catch (NoSuchPaddingException e) {
            log.error("公钥算法-无效的补位算法:", e);
        } catch (IllegalBlockSizeException e) {
            log.error("公钥算法-无效的块大小:", e);
        } catch (BadPaddingException e) {
            log.error("公钥算法-补位算法异常:", e);
        } catch (InvalidKeyException e) {
            log.error("公钥算法-无效的私钥:", e);
        }
        return null;
    }
    /**
     * 获取key
     *
     * @return
     */
    protected KeyStoreInfo getKeyInfo(BaoFuRequest source) {
        try {
            return keyStoreLoadManager.load(source.getMemberId(), BaoFuConstants.PROVIDER_NAME);
        } catch (Exception e) {
            log.info("商户partnerId={}密钥加载失败,启用配置文件密钥", source.getMemberId());
            return getKeyStoreInfo();
        }
    }

    /**
     * 获取keyStoreInfo
     *
     * @return
     */
    protected KeyStoreInfo getKeyInfo(String partnerId) {
        try {
            return keyStoreLoadManager.load(partnerId, BaoFuConstants.PROVIDER_NAME);
        } catch (Exception e) {
            log.info("商户partnerId={}密钥加载失败,启用配置文件密钥", partnerId);
            return getKeyStoreInfo();
        }
    }

    protected void beforeMarshall(BaoFuApiMessage message) {

    }

    public KeyStoreInfo getKeyStoreInfo() {
        if (null == keyStoreInfo) {
            synchronized (BaoFuMarshallSupport.class) {
                if (keyStoreInfo == null) {
                    keyStoreInfo = new KeyStoreInfo();
                    keyStoreInfo.setKeyStoreUri(openAPIClientBaoFuProperties.getPrivateKeyPath());
                    keyStoreInfo.setKeyStorePassword(openAPIClientBaoFuProperties.getPrivateKeyPassword());
                    keyStoreInfo.setCertificateUri(openAPIClientBaoFuProperties.getPublicKeyPath());
                    keyStoreInfo.setKeyStoreType(KeyStoreInfo.KEY_STORE_PKCS12);
                    keyStoreInfo.setPlainEncode("utf-8");
                    keyStoreInfo.setSignatureAlgo(RSA.SIGN_ALGO_SHA1);
                    keyStoreInfo.setSignatureCodec(CodecEnum.BASE64);

                    // 最后load下，内部会缓存。
                    keyStoreInfo.loadKeys();
                }
            }

        }
        return keyStoreInfo;
    }
}
